„Huh?“ you ask. Yes, it is actually possible to do functional programming in Vim. Of course this is not as convenient as in a language which is actually designed for functional programming like Haskell or Clojure. But nevertheless: it is possible!
A main building block for functional programming are closures. Consider for example this clojure snippet.
(let [x 5
y 6]
(future
(+ x y)))
Under the hood, Clojure creates a thunk containing the
body of the future
call, that is (+ x y)
. This thunk is
passed to a freshly created Future object, which sets up a new
thread and calls the thunk in the new thread. Since the thunk
is a closure it remembers the environment where it was defined.
So although the thunk is executed in a different thread, it has
access to the locals x
and y
.
Now Vim doesn't have closures. You can define function objects, but they don't remember their environment. However we can manually set up a poor man's closure by virtue of Vim's maps and 1st order functions.
That means, that we have to manually close over all values from the environment, which we later on need, when executing the function. To do that, we simply set up a map containing the required values. Plus the function.
function CounterBump() dict
let cnt = self.counter
let self.counter += 1
return cnt
endfunction
function MakeCounter(initial)
let closure = { counter: a:initial }
let closure.bump = function("MakeCounterBump")
return closure
endfunction
MakeCounter
returns a closure, which „closes over the local
counter
variable“. It should pretty obvious what happens. The
function CounterBump
accesses a well defined set of key in
the map to do its work. The MakeCounter
constructor sets up
the required keys and adds the function to the map.
Let's use our new counter.
let counter1 = MakeCounter(5)
counter1.bump() " gives 5
counter1.bump() " gives 6
let counter2 = MakeCounter(0)
counter2.bump() " gives 0
counter1.bump() " gives 7
This different calls to MakeCounter
give different maps. Hence
the CounterBump
function sees different counter
locals.
Suppose you want to move some text from one buffer to another. This could be done be yanking the text into a register and paste into the target buffer. However, what happens in that case with the register content? Maybe the user saved already something there?
So we have to protect the register's content. This adds a lot of annoying boilerplate. Save the content, do things, restore the register.
This can be hidden away in with the above technique. Consider the following helper.
function WithSavedRegister(register, closure)
let savedReg = getreg(a:register)
let result = closure.f()
call setreg(a:register, savedReg)
return result
endfunction
The usage should be fairly obvious by now. We first define a
worker function. Then create our „closure“ map with a well
defined f
key and all other necessary things. Finally we pass
everything to our WithSavedRegister
utility to actually do
the work.
function Worker() dict
" do stuff here
return "something useful"
endfunction
let closure = { f: function("Worker") }
let somethingUseful = WithSavedRegister("x", closure)
Ooops. But what happens if an exception is thrown in our
Worker
? The register will not be restored. If we had sprinkled
our code with such boilerplate, we would now be in trouble.
We'd have to go to each and everyplace and add the protection.
But with our utility it is sufficient to fix WithSavedRegister
.
function WithSavedRegister(register, closure)
let savedReg = getreg(a:register)
try
let result = closure.f()
finally
call setreg(a:register, savedReg)
endtry
return result
endfunction
Functional programming in Vim is possible, although it is not really supported out of the box. Lightweight objects in form of maps and function pointers make this possible.
Published by Meikel Brandmeyer on .
I'm a long-time Clojure user and the developer of several open source projects mostly involving Clojure. I try to actively contribute to the Clojure community.
My most active projects are at the moment VimClojure, Clojuresque and ClojureCheck.
Copyright © 2009-2014 All Right Reserved. Meikel Brandmeyer